#include "Rp2Shader.h"

using namespace Rp2;

RP2_IMPLEMENT_RTTI(Rp2, Shader, Object);
RP2_IMPLEMENT_DEFAULT_NAME_ID(Shader, Object);

//---------------------------------------------------------------------------------------------------
Shader::Shader()
{
	// Create shader objects
	m_acShaderText = 0;
	m_uiProgram = 0;
	m_uiNumVerts = 0;
	m_uiVertexShader = glCreateShader(GL_VERTEX_SHADER);
	m_uiPixelShader = glCreateShader(GL_FRAGMENT_SHADER);
}
//---------------------------------------------------------------------------------------------------
Shader::Shader (const std::string& rkShaderName)
    :
    m_kShaderName(rkShaderName)
{

}
//---------------------------------------------------------------------------------------------------
Shader::Shader(GLint uiNumVerts)
{
	// Create shader objects
	m_acShaderText = 0;
	m_uiProgram = 0;
	m_uiNumVerts = uiNumVerts;
	m_uiVertexShader = glCreateShader(GL_VERTEX_SHADER);
	m_uiPixelShader = glCreateShader(GL_FRAGMENT_SHADER);
}
//---------------------------------------------------------------------------------------------------
Shader::~Shader()
{
	if (m_uiProgram)
	{
		glDeleteProgram(m_uiProgram);
	}
	if (m_acShaderText)
	{
		delete m_acShaderText;
	}
}
//---------------------------------------------------------------------------------------------------
bool Shader::SetTexture(int i, Texture* pkTexture)
{
	if (pkTexture)
	{
		if (0 <= i && i < (int)m_kTextures.size())
		{
			m_kTextures[i] = pkTexture;
			return true;
		}
	}
	return false;
}
//---------------------------------------------------------------------------------------------------
bool Shader::SetTexture (int i, const std::string& rkName)
{
    if (0 <= i && i < (int)m_kTextures.size())
    {
        Texture* pkTexture = Catalog<Texture>::GetActive()->Find(rkName);
        if (pkTexture)
		{
            m_kTextures[i] = pkTexture;
            return true;
        }
    }
	else
	{
		System::MsgBoxPrintf(TC("Shader::SetTexture"),TC("%s texture index error"),
			rkName.c_str());
	}
    return false;
}
//---------------------------------------------------------------------------------------------------
Texture* Shader::GetTexture (int i)
{
    if (0 <= i && i < (int)m_kTextures.size())
    {
        return m_kTextures[i];
    }
    return 0;
}
//---------------------------------------------------------------------------------------------------
const Texture* Shader::GetTexture (int i) const
{
    if (0 <= i && i < (int)m_kTextures.size())
    {
        return m_kTextures[i];
    }
    return 0;
}
//---------------------------------------------------------------------------------------------------
Texture* Shader::GetTexture (const std::string& rkName)
{
    for (int i = 0; i < (int)m_kTextures.size(); i++)
    {
        if (m_kTextures[i] && m_kTextures[i]->GetName() == rkName)
        {
            return m_kTextures[i];
        }
    }
    return 0;
}
//---------------------------------------------------------------------------------------------------
const Texture* Shader::GetTexture (const std::string& rkName) const
{
    for (int i = 0; i < (int)m_kTextures.size(); i++)
    {
        if (m_kTextures[i] && m_kTextures[i]->GetName() == rkName)
        {
            return m_kTextures[i];
        }
    }
    return 0;
}
//---------------------------------------------------------------------------------------------------
void Shader::OnLoadProgram()
{
    // The data sources must be set for the user constants.  Determine how
    // many float channels are needed for the storage.
    int iUCQuantity = m_spkProgram->GetUserConstantQuantity();
    int i, iChannels;
    UserConstant* pkUC;
    for (i = 0, iChannels = 0; i < iUCQuantity; i++)
    {
        pkUC = m_spkProgram->GetUserConstant(i);
        assert(pkUC);
        iChannels += 4*pkUC->GetRegisterQuantity();
    }
    m_kUserData.resize(iChannels);

    // Set the data sources for the user constants.
    for (i = 0, iChannels = 0; i < iUCQuantity; i++)
    {
        pkUC = m_spkProgram->GetUserConstant(i);
        assert(pkUC);
        pkUC->SetDataSource(&m_kUserData[iChannels]);
        iChannels += 4*pkUC->GetRegisterQuantity();
    }

    // The number of textures is the number of samplers required by the
    // program.
    int iSIQuantity = m_spkProgram->GetSamplerInformationQuantity();
    if (iSIQuantity > 0)
    {
        m_kTextures.resize(iSIQuantity);
	}
}
//---------------------------------------------------------------------------------------------------
void Shader::BindBuffers(GLenum eAttribute, GLfloat* pfBuffer)
{
	GLuint uiChannels;
	
	switch (eAttribute)
	{
	case Attributes::ATB_COLOR_3:
	case Attributes::ATB_VERTEX_3:
		uiChannels = 3;
		break;
	case Attributes::ATB_VERTEX_4:
	case Attributes::ATB_COLOR_4:
		uiChannels = 4;
		break;
	}
	// copy  data
	GLuint uiArray = 0;
	glGenBuffers(1, &uiArray);
	glBindBuffer(GL_ARRAY_BUFFER, uiArray);
	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * uiChannels * m_uiNumVerts, pfBuffer, GL_DYNAMIC_DRAW);

	if (uiArray)
	{
		// 0 for vertex
		glEnableVertexAttribArray(eAttribute);
		glBindBuffer(GL_ARRAY_BUFFER, uiArray);
		glVertexAttribPointer(eAttribute, uiChannels, GL_FLOAT, GL_FALSE, 0, 0);
	}
}
//---------------------------------------------------------------------------------------------------
GLuint Shader::CreateProgram(char* acVertexShader, char* acPixelShader, ...)
{
	// Load shaders from desk
	if (!LoadShader(acVertexShader, m_uiVertexShader))
	{
		System::MsgBoxPrintf(TC("error"),TC("Error when loading shader %s"), acVertexShader);
		glDeleteShader(m_uiVertexShader);
		glDeleteShader(m_uiPixelShader);
		return false;
	}
	if (!LoadShader(acPixelShader, m_uiPixelShader))
	{
		System::MsgBoxPrintf(TC("error"),TC("Error when loading shader %s"), acPixelShader);
		glDeleteShader(m_uiVertexShader);
		glDeleteShader(m_uiPixelShader);
		return false;
	}
	
	// Compile shaders
	glCompileShader(m_uiVertexShader);	
	glCompileShader(m_uiPixelShader);
	// Check for compile errors
	GLint iError;
	// Check vertex shader
	glGetShaderiv(m_uiVertexShader, GL_COMPILE_STATUS, &iError);
	if (iError == GL_FALSE)
	{
		char Info[1024];
		glGetShaderInfoLog(m_uiVertexShader, 1024, 0, Info);
		System::MsgBoxPrintf(RP2_ERROR, TC("The shader %s failed to compile with following errors:\n%s\n"), acVertexShader, Info);
		glDeleteShader(m_uiVertexShader);
		glDeleteShader(m_uiPixelShader);
		return GL_FALSE;
	}
	// Check Pixel shader
	glGetShaderiv(m_uiPixelShader, GL_COMPILE_STATUS, &iError);
	if (iError == GL_FALSE)
	{
		char Info[1024];
		glGetShaderInfoLog(m_uiPixelShader, 1024, 0, Info);
		System::MsgBoxPrintf(RP2_ERROR, TC("The shader %s failed to compile with following errors:\n%s\n"), acPixelShader, Info);
		glDeleteShader(m_uiVertexShader);
		glDeleteShader(m_uiPixelShader);
		return GL_FALSE;
	}	

	// Get the handle of shader program
	m_uiProgram = glCreateProgram();
	// Attach shaders
	glAttachShader(m_uiProgram, m_uiVertexShader);
	glAttachShader(m_uiProgram, m_uiPixelShader);

	// Bind attributes
	va_list AttributesList;
	va_start(AttributesList, acPixelShader);
	char* acNextArg;
	// first arg, number of attributes 
	int iArgCount = va_arg(AttributesList, int);
	for (int i = 0; i < iArgCount; i++)
	{
		int iIndex = va_arg(AttributesList, int);
		acNextArg = va_arg(AttributesList, char*);
		glBindAttribLocation(m_uiProgram, iIndex, acNextArg);
	}
	va_end(AttributesList);

	// Link
	glLinkProgram(m_uiProgram);
	glGetProgramiv(m_uiProgram, GL_LINK_STATUS, &iError);
	if (iError == GL_FALSE)
	{
		char Info[1024];
		glGetProgramInfoLog(m_uiProgram, 1024, 0, Info);
		System::MsgBoxPrintf(TC("Error"),TC("Failed when link program %s and %s"), acVertexShader, acPixelShader);
		glDeleteProgram(m_uiProgram);
	}

	// these are no longer needed
	glDeleteShader(m_uiVertexShader);
	glDeleteShader(m_uiPixelShader);

	// all done
	return m_uiProgram;
}
//---------------------------------------------------------------------------------------------------
bool Shader::LoadShader(const char* acFileName, GLuint uiShader)
{
	FILE* fp;
	int ShaderLength = 0;
	
	// Open program file
	fopen_s(&fp, acFileName, "r");
	if (!fp)
	{
		return false;
	}
	else
	{
		while (fgetc(fp) !=	EOF)
		{
			ShaderLength++;
		}
		// Back to beginning of file
		rewind(fp);
		
		// Read the file
		m_acShaderText = (char*)malloc(ShaderLength * sizeof(char));
		fread(m_acShaderText, 1, ShaderLength, fp);

		// make sure it's null terminated
		m_acShaderText[ShaderLength] = '\0';

		//char* pacShaderText[1];
		//pacShaderText[0] = m_acShaderText;
		char** pacShaderText  = (char**)malloc(sizeof(m_acShaderText));
		*pacShaderText = m_acShaderText;

		glShaderSource(uiShader, 1, (const GLchar**) pacShaderText, 0);	
		// free tmp pointer.
		free(pacShaderText);
		fclose(fp);
	}
	return true;
}
